﻿using MDK.Common;
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace MDK.Device.Devices
{
    public interface ICentrifuge
    {
        /// <summary>
        /// 转速,rpm
        /// </summary>
        int Speed { get; set; }
        /// <summary>
        /// 离心时间,min
        /// </summary>
        int RunTime { get; set; }
        /// <summary>
        /// 离心温度,℃
        /// </summary>
        double Temperature { get; set; }
        /// <summary>
        /// 不平衡值
        /// </summary>
        int Balance { get; set; }
        /// <summary>
        /// 实时转速,rpm
        /// </summary>
        int CurrentSpeed { get; }
        /// <summary>
        /// 剩余离心时间,min
        /// </summary>
        int SurplusTime { get; }
        /// <summary>
        /// 已离心时间,min
        /// </summary>
        int ElapseTime { get; }
        /// <summary>
        /// 实时温度,℃
        /// </summary>
        double CurrentTemperature { get; }
        CentrifugeStatus CurrentState { get; }
        void StartOpenDoor();
        void StartCloseDoor();
        CentrifugeDoorLocations GetDoorState();
        void TurnOnCompressor();
        void TurnOffCompressor();
        bool CompressorIsOn();
        void GotoStation(int station);
        int GetStation();
        void Begin();
        void Stop();
        int GetErrorCode();
        void ClearError();
        event CommunicationErrorHandler CommunicationError;
        bool IsFree { get; set; }
        int UseCount { get; set; }
    }

    [Flags]
    public enum CentrifugeDoorLocations
    {
        Running = 0,
        Opened = 1,
        Closed = 2,
        Foreign = 4
    }

    public enum CentrifugeStatus
    {
        Stop = 0,
        Recognize = 1,
        Break = 2,
        SpeedUp = 3,
        ConstantSpeed = 4,
        SpeedCut = 5,
        SlowSpeed = 6,
        Inching = 7,
        Station0 = 0,
        Station1 = 9,
        Station2 = 10,
        Station3 = 11,
        Station4 = 12,
    }

    public class Centrifuge : ICentrifuge
    {
        private TcpClient _tcpClient;
        private NetworkStream _stream;

        public string IP { get; }

        public int Port { get; }
        /// <summary>
        /// 是否启用
        /// </summary>
        public bool IsEnable { get; set; }

        public TimeSpan Timeout { get; set; }

        public Centrifuge(string ip, int port = 3010)
        {
            IP = ip;
            Port = port;
            Timeout = TimeSpan.FromSeconds(10);
        }


        private static readonly int[] _balanceLayoutCode =
        {
            0x00000, 0x00200, 0x00600, 0x00700, 0x00F00, 0x04720, 0x04F20, 0x06730, 0x0E670, 0x0E770, 0x0EF70,
            0x4E772, 0x4EF72, 0x1F7F8, 0x1FFF8, 0x5F7FA, 0x5FFFA, 0x7F7FB, 0x7FFFB, 0xFF7FF, 0xFFFFF
        };

        #region 命令常量

        private static readonly byte[] _cmdOpenDoor = 
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x05, 0x00, 0x02, 0xFF, 0x00 };  // 开门
        private static readonly byte[] _cmdCloseDoor = 
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x05, 0x00, 0x02, 0x00, 0x00 };  // 关门
        private static readonly byte[] _cmdGetDoorState = 
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x17, 0x00, 0x01 };  // 读门状态
        private static readonly byte[] _cmdOnCompressor = 
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x05, 0x00, 0x0B, 0xFF, 0x00 };  // 启动压缩机
        private static readonly byte[] _cmdOffCompressor = 
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x05, 0x00, 0x0B, 0x00, 0x00 };  // 停止压缩机
        private static readonly byte[] _cmdGetCompressorState =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x0C, 0x00, 0x01 };  // 读压缩机状态
        private static readonly byte[] _cmdSetBalance =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x06, 0x00, 0x2F, 0x00, 0x00 };  // 设平衡阈值
        private static readonly byte[] _cmdSetSpeed =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x06, 0x00, 0x37, 0x00, 0x00 };  // 设离心速度
        private static readonly byte[] _cmdSetRunTime =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x06, 0x00, 0x38, 0x00, 0x00 };  // 设离心时间
        private static readonly byte[] _cmdSetTemperature =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x06, 0x00, 0x39, 0x00, 0x00 };  // 设离心温度
        private static readonly byte[] _cmdGetBalance =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x2F, 0x00, 0x01 };  // 读平衡阈值
        private static readonly byte[] _cmdGetSpeed =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x37, 0x00, 0x01 };  // 读离心速度
        private static readonly byte[] _cmdGetRunTime =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x38, 0x00, 0x01 };  // 读离心时间
        private static readonly byte[] _cmdGetTemperature =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x39, 0x00, 0x01 };  // 读离心温度
        private static readonly byte[] _cmdGetStaticBalance =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x10, 0x00, 0x01 };  // 读静态平衡值
        private static readonly byte[] _cmdGetNearBalance =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x11, 0x00, 0x01 };  // 读接近平衡值
        private static readonly byte[] _cmdGetEscapeBalance =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x12, 0x00, 0x01 };  // 读逃逸平衡值
        private static readonly byte[] _cmdGetCurrentSpeed =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x18, 0x00, 0x01 };  // 读当前速度
        private static readonly byte[] _cmdGetElapseTime =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x1A, 0x00, 0x01 };  // 读已离心时间
        private static readonly byte[] _cmdGetSurplusTime =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x1B, 0x00, 0x01 };  // 读剩余离心时间
        private static readonly byte[] _cmdGetCurrentTemperature =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x19, 0x00, 0x01 };  // 读当前温度
        private static readonly byte[] _cmdGetErrorCode =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x05, 0x00, 0x01 };  // 读故障代码
        private static readonly byte[] _cmdClearError =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x05, 0x00, 0x01, 0xFF, 0x00 };  // 清除错误
        private static readonly byte[] _cmdSetRotorState =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00 };  // 写工位状态
        private static readonly byte[] _cmdGetCurrentRotorState =
            { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x03, 0x00, 0x01 };  // 读转子状态

        private byte[] ExecuteCmd(byte[] cmd)
        {
            var errorcode = 0;
            var retryCount = 0;
            var buffer = cmd[5] == 0x09 ? new byte[12] : new byte[11];
            while (true)
            {
                var startTime = DateTime.Now;
                // 连接TCP
                if (_stream == null || _tcpClient == null)
                {
                    _tcpClient = new TcpClient(new IPEndPoint(IPAddress.Parse("0.0.0.0"), 0));
                    ThreadPool.QueueUserWorkItem(arg =>
                    {
                        var client = (TcpClient)arg;
                        while (!client.Connected)
                        {
                            if (DateTime.Now - startTime > Timeout)
                            {
                                errorcode = ErrorCodeHelper.TcpConnectTimeout;
                                client.Close();
                                break;
                            }
                        }
                    }, _tcpClient);
                    try
                    {
                        _tcpClient.Connect(IP, Port);
                        _stream = _tcpClient.GetStream();
                    }
                    catch (Exception e)
                    {
                        _tcpClient = null;
                        _stream = null;
                        goto lbl_error;
                    }
                }

                try
                {
                    while (_stream.DataAvailable)
                    {
                        _stream.ReadByte();
                    }
                    _stream.Write(cmd, 0, cmd.Length);
                    while (!_stream.DataAvailable)
                    {
                        if (DateTime.Now - startTime > Timeout)
                        {
                            errorcode = ErrorCodeHelper.DeviceTimeout;
                            goto lbl_error;
                        }
                    }

                    var respLen = _stream.Read(buffer, 0, buffer.Length);

                    if (respLen != buffer.Length)
                    {
                        errorcode = ErrorCodeHelper.TcpFrameError;
                        goto lbl_error;
                    }

                    return buffer;
                }
                catch (Exception)
                {
                    errorcode = ErrorCodeHelper.TcpComError;
                    _tcpClient?.Close();
                    _stream = null;     // 强制重连
                    _tcpClient = null;
                }
                
lbl_error:
                var retry = OnCommunicationError(errorcode, retryCount);
                if (retry)
                {
                    retryCount++;
                }
                else
                {
                    return null;
                }
            }
        }

        private int ExecuteReadCmd(byte[] cmd)
        {
            var value = int.MinValue;
            var resp = ExecuteCmd(cmd);
            if (resp != null)
            {
                value = (resp[9] << 8) + resp[10];
            }

            return value;
        }
        
        private void ExecuteWriteCmd(byte[] cmd, int value)
        {
            cmd[10] = (byte)((value >> 8) & 0xFF);
            cmd[11] = (byte)(value & 0xFF);
            ExecuteCmd(cmd);
        }

        #endregion

        public int Speed
        {
            get { return ExecuteReadCmd(_cmdGetSpeed); }
            set { ExecuteWriteCmd(_cmdSetSpeed, value); }
        }
        public int RunTime
        {
            get { return ExecuteReadCmd(_cmdGetRunTime); }
            set { ExecuteWriteCmd(_cmdSetRunTime, value); }
        }
        public double Temperature
        {
            get { return ((double)ExecuteReadCmd(_cmdGetTemperature)); }
            set { ExecuteWriteCmd(_cmdSetTemperature, (int)value); }
        }
        public int Balance
        {
            get { return ExecuteReadCmd(_cmdGetBalance); }
            set { ExecuteWriteCmd(_cmdSetBalance, value); }
        }
        public int CurrentSpeed
        {
            get { return ExecuteReadCmd(_cmdGetCurrentSpeed); }
        }

        public int SurplusTime
        {
            get { return ExecuteReadCmd(_cmdGetSurplusTime); }
        }

        public int ElapseTime
        {
            get { return ExecuteReadCmd(_cmdGetElapseTime); }
        }

        public double CurrentTemperature
        {
            get { return ((double) ExecuteReadCmd(_cmdGetCurrentTemperature) / 10); }
        }

        public int BalanceStatics
        {
            get { return ExecuteReadCmd(_cmdGetStaticBalance); }
        }

        public int BalanceNear
        {
            get { return ExecuteReadCmd(_cmdGetNearBalance); }
        }

        public int BalanceEscape
        {
            get { return ExecuteReadCmd(_cmdGetEscapeBalance); }
        }

        public CentrifugeStatus CurrentState
        {
            get { return (CentrifugeStatus) ExecuteReadCmd(_cmdGetCurrentRotorState); }
        }

        public void StartOpenDoor()
        {
            ExecuteCmd(_cmdOpenDoor);
        }

        public void StartCloseDoor()
        {
            ExecuteCmd(_cmdCloseDoor);
        }

        public CentrifugeDoorLocations GetDoorState()
        {
            return (CentrifugeDoorLocations)ExecuteReadCmd(_cmdGetDoorState);
        }

        public void TurnOnCompressor()
        {
            ExecuteCmd(_cmdOnCompressor);
        }

        public void TurnOffCompressor()
        {
            ExecuteCmd(_cmdOffCompressor);
        }

        public bool CompressorIsOn()
        {
            return ExecuteReadCmd(_cmdGetCompressorState) != 0;
        }

        /*  走工位等6秒补丁  */
        private DateTime _lastGotoStationTime = DateTime.MinValue;
        private TimeSpan _minInterval = TimeSpan.FromSeconds(6);

        public void GotoStation(int station)
        {
            if (station < 0 || station > 4) return;
            ExecuteWriteCmd(_cmdSetRotorState, station + 2);
            _lastGotoStationTime = DateTime.Now;
        }

        public int GetStation()
        {
            if (DateTime.Now - _lastGotoStationTime < _minInterval) return -1;
            return ExecuteReadCmd(_cmdGetCurrentRotorState) - 8;
        }

        public void Begin()
        {
            HandoverData.SandCentrifugeData(EnumCmdCode.Centrifuge, IP, this.GetType().Name, "Centrifuge", 1, (int)(Temperature*10), Speed, Balance, RunTime);
            ExecuteWriteCmd(_cmdSetRotorState, 1);
        }

        public void Stop()
        {
            ExecuteWriteCmd(_cmdSetRotorState, 0);
        }
        public int GetErrorCode()
        {
            return ExecuteReadCmd(_cmdGetErrorCode);
        }

        public void ClearError()
        {
            ExecuteCmd(_cmdClearError);
        }

        public event CommunicationErrorHandler CommunicationError;
        
        public static int GetBalanceCode(int tubeCount)
        {
            return _balanceLayoutCode[tubeCount];
        }

        public bool IsFree { get; set; } = true;
        public int UseCount { get; set; }

        protected virtual bool OnCommunicationError(int errorcode, int retryCount)
        {
            var handler = CommunicationError;
            if (handler != null)
            {
                return handler.Invoke(this, errorcode, retryCount);
            }

            return false;
        }
    }

    public static class CentrifugeHelper
    {
        public static string GetErrorCodeText(int errorcode)
        {
            switch (errorcode)
            {
                case 0: return "执行成功";
                case -1: return "TCP连接超时";
                case -2: return "设备响应超时";
                case -3: return "TCP通信错误";
                case -4: return "帧错误";
                default: return "未知错误";
            }
        }
    }
}